/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jdbi.v3.sqlobject; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.UUID; import org.h2.jdbcx.JdbcDataSource; import org.jdbi.v3.core.CloseException; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.core.JdbiException; import org.jdbi.v3.core.Something; import org.jdbi.v3.core.mapper.RowMapper; import org.jdbi.v3.core.mapper.SomethingMapper; import org.jdbi.v3.core.result.ResultIterator; import org.jdbi.v3.core.spi.JdbiPlugin; import org.jdbi.v3.core.statement.StatementContext; import org.jdbi.v3.core.transaction.TransactionException; import org.jdbi.v3.sqlobject.customizer.Bind; import org.jdbi.v3.sqlobject.locator.UseClasspathSqlLocator; import org.jdbi.v3.sqlobject.statement.SqlQuery; import org.jdbi.v3.sqlobject.statement.SqlUpdate; import org.jdbi.v3.sqlobject.statement.UseRowMapper; import org.jdbi.v3.sqlobject.transaction.Transactional; import org.junit.After; import org.junit.Before; import org.junit.Test; public class TestOnDemandSqlObject { private Jdbi db; private Handle handle; private final HandleTracker tracker = new HandleTracker(); private JdbcDataSource ds; @Before public void setUp() throws Exception { ds = new JdbcDataSource(); // in MVCC mode h2 doesn't shut down immediately on all connections closed, so need random db name ds.setURL(String.format("jdbc:h2:mem:%s;MVCC=TRUE", UUID.randomUUID())); db = Jdbi.create(ds); db.installPlugin(new SqlObjectPlugin()); handle = db.open(); handle.execute("create table something (id int primary key, name varchar(100))"); db.installPlugin(tracker); } @After public void tearDown() throws Exception { handle.close(); } @Test public void testAPIWorks() throws Exception { Spiffy s = db.onDemand(Spiffy.class); s.insert(7, "Bill"); String bill = db.open().createQuery("select name from something where id = 7").mapTo(String.class).findOnly(); assertThat(bill).isEqualTo("Bill"); } @Test(expected=TransactionException.class) public void testExceptionOnClose() throws Exception { JdbiPlugin plugin = new JdbiPlugin() { @Override public Handle customizeHandle(Handle handle) { Handle h = spy(handle); when(h.createUpdate(anyString())).thenThrow(new TransactionException("connection reset")); doThrow(new CloseException("already closed", null)).when(h).close(); return h; } }; db.installPlugin(plugin); Spiffy s = db.onDemand(Spiffy.class); s.insert(1, "Tom"); } @Test public void testIteratorCloseHandleOnError() throws Exception { Spiffy s = db.onDemand(Spiffy.class); assertThatExceptionOfType(JdbiException.class).isThrownBy(s::crashNow); assertThat(tracker.hasOpenedHandle()).isFalse(); } @Test public void testIteratorClosedOnReadError() throws Exception { Spiffy spiffy = db.onDemand(Spiffy.class); spiffy.insert(1, "Tom"); Iterator<Something> i = spiffy.crashOnFirstRead(); assertThatExceptionOfType(JdbiException.class).isThrownBy(i::next); assertThat(tracker.hasOpenedHandle()).isFalse(); } @Test public void testIteratorClosedIfEmpty() throws Exception { Spiffy spiffy = db.onDemand(Spiffy.class); spiffy.findAll(); assertThat(tracker.hasOpenedHandle()).isFalse(); } @Test public void testIteratorPrepatureClose() throws Exception { Spiffy spiffy = db.onDemand(Spiffy.class); spiffy.insert(1, "Tom"); try (ResultIterator<Something> all = spiffy.findAll()) {} assertThat(tracker.hasOpenedHandle()).isFalse(); } @Test public void testSqlFromExternalFileWorks() throws Exception { Spiffy spiffy = db.onDemand(Spiffy.class); ExternalSql external = db.onDemand(ExternalSql.class); spiffy.insert(1, "Tom"); spiffy.insert(2, "Sam"); List<Something> all = external.findAll(); assertThat(all).hasSize(2); } public interface Spiffy { @SqlUpdate("insert into something (id, name) values (:id, :name)") void insert(@Bind("id") long id, @Bind("name") String name); @SqlQuery("select name, id from something") @UseRowMapper(SomethingMapper.class) ResultIterator<Something> findAll(); @SqlQuery("select * from crash now") @UseRowMapper(SomethingMapper.class) Iterator<Something> crashNow(); @SqlQuery("select name, id from something") @UseRowMapper(CrashingMapper.class) Iterator<Something> crashOnFirstRead(); } public interface TransactionStuff extends Transactional<TransactionStuff> { @SqlQuery("select id, name from something where id = :id") @UseRowMapper(SomethingMapper.class) Something byId(@Bind("id") long id); @SqlUpdate("update something set name = :name where id = :id") void updateName(@Bind("id") long id, @Bind("name") String name); @SqlUpdate("insert into something (id, name) values (:id, :name)") void insert(@Bind("id") long id, @Bind("name") String name); } @UseClasspathSqlLocator public interface ExternalSql { @SqlQuery("all-something") @UseRowMapper(SomethingMapper.class) List<Something> findAll(); } public static class CrashingMapper implements RowMapper<Something> { @Override public Something map(ResultSet r, StatementContext ctx) throws SQLException { throw new SQLException("fake protocol error"); } } static class HandleTracker implements JdbiPlugin { final List<Handle> openedHandle = new ArrayList<>(); @Override public Handle customizeHandle(Handle handle) { openedHandle.add(handle); return handle; } boolean hasOpenedHandle() throws SQLException { for (Handle h : openedHandle) { if (!h.getConnection().isClosed()) return true; } return false; } } }